#ifndef VECTOR2_HPP
#define VECTOR2_HPP

#include <iostream>
#include <iomanip>
#include <cmath>

#include "../NeonCore/Global.hpp"

namespace Neon
{
/*!
* Defines a Vector with 2 axis.
*/
class NEON_DLL Vector2
{
public:
	/*!
	Creates a Zero Vector2.
	*/
	Vector2();

	/*!
	Creates a custom Vector2.
	*\param x X axis value.
	*\param y Y axis value.
	*/
	explicit Vector2(const nfloat x, const nfloat y);

	/*!
	Creates a Vector2 from another Vector2.
	*\param vec Source vector.
	*/
	Vector2(const Vector2 &vec);

	/*!
	Creates a Vector2 from another Vector2.
	*\param rhs Source vector.
	*\return The copied vector.
	*/
	Vector2& operator =(const Vector2 &rhs);

	/*!
	Inverts negative values to postive.
	*\return A postive vector.
	*/
	Vector2 operator +() const;

	/*!
	Adds two Vector2.
	*\param rhs Source vector.
	*\return Sum of two vectors.
	*/
	Vector2 operator +(const Vector2 &rhs) const;

	/*!
	Adds a Vector2 and a scalar.
	*\param rhs Vector scalar.
	*\return Sum of a vector and a scalar.
	*/
	Vector2 operator +(nfloat rhs) const;

	/*!
	Adds a scalar and a Vector2.
	*\param lhs Vector scalar.
	*\param rhs Source vector.
	*\return Sum of a scalar and a vector.
	*/
	friend Vector2 operator +(nfloat lhs, const Vector2 &rhs);

	/*!
	Adds two Vector2.
	*\param rhs Source vector.
	*\return Sum of two vectors.
	*/
	Vector2& operator +=(const Vector2 &rhs);

	/*!
	Adds a Vector2 and a scalar.
	*\param rhs Vector scalar.
	*\return Sum of a vector and a scalar.
	*/
	Vector2& operator +=(nfloat rhs);

	/*!
	Inverts postive values to negative.
	*\return A negative vector.
	*/
	Vector2 operator -() const;

	/*!
	Subtracts two vectors.
	*\param rhs Source vector.
	*\return Result of the subtraction.
	*/
	Vector2 operator -(const Vector2 &rhs) const;

	/*!
	Subtracts a Vector and a scalar.
	*\param rhs Vector scalar.
	*\return Result of the subtraction.
	*/
	Vector2 operator -(nfloat rhs) const;

	/*!
	Subtracts a scalar and a Vector.
	*\param lhs Vector scalar.
	*\param rhs Source vector.
	*\return Result of subtraction.
	*/
	friend Vector2 operator -(nfloat lhs, const Vector2 &rhs);

	/*!
	Subtracts two vectors.
	*\param rhs Source vector.
	*\return Result of the subtraction.
	*/
	Vector2& operator -=(const Vector2 &rhs);

	/*!
	Subtracts a Vector and a scalar.
	*\param rhs Vector scalar.
	*\return Result of the subtraction.
	*/
	Vector2& operator -=(nfloat rhs);

	/*!
	Multiplies two Vectors.
	*\param rhs Source vector.
	*\return Result of the multiplication.
	*/
	Vector2 operator *(const Vector2 &rhs) const;

	/*!
	Multiplies a Vector by a scalar.
	*\param rhs Vector scalar.
	*\return Result of the multiplication.
	*/
	Vector2 operator *(nfloat rhs) const;

	/*!
	Multiplies a scalar by a Vector.
	*\param lhs Vector scalar.
	*\param rhs Source vector.
	*\return Result of the multiplication.
	*/
	friend Vector2 operator *(nfloat lhs, const Vector2 &rhs);

	/*!
	Multiplies two Vectors.
	*\param rhs Source vector.
	*\return Result of the multiplication.
	*/
	Vector2& operator *=(const Vector2 &rhs);

	/*!
	Multiplies a Vector by a scalar.
	*\param rhs Vector scalar.
	*\return Result of the multiplication.
	*/
	Vector2& operator *=(nfloat rhs);

	/*!
	Divides two Vectors.
	*\param rhs Source vector.
	*\return Result of the Division.
	*/
	Vector2 operator /(const Vector2 &rhs) const;

	/*!
	Divides a Vector by a scalar.
	*\param rhs Vector scalar.
	*\return Result of the Division.
	*/
	Vector2 operator/(nfloat rhs) const;

	/*!
	Divides two Vectors.
	*\param rhs Source vector.
	*\return Result of the Division.
	*/
	Vector2& operator /=(const Vector2 &rhs);

	/*!
	Divides a Vector by a scalar.
	*\param rhs Vector scalar.
	*\return Result of the Division.
	*/
	Vector2& operator /=(nfloat rhs);

	/*!
	Tests two Vector2 for equality.
	*\param rhs Source vector.
	*\return true if the vectors are equal; false otherwise.
	*/
	bool operator ==(const Vector2 &rhs) const;

	/*!
	Tests two Vector2 for inequality.
	*\param rhs Source vector.
	*\return true if the vectors are not equal; false otherwise.
	*/
	bool operator !=(const Vector2 &rhs) const;

	/*!
	Prints a Vector2 to the console.
	*\param ostr Stream buffer.
	*\param vec Source vector.
	*\return A stream buffer of the vector.
	*/
	friend std::ostream& operator <<(std::ostream &ostr, const Vector2 &vec);

	/*!
	Returns a Vector2 containing the 2D Cartesian coordinates of a point specified in 2S barycentric (areal) coordinates.
	*\param vec1 Containing the 2D Cartesian coordinates of vertex 1 of the triangles.
	*\param vec2 Containing the 2D Cartesian coordinates of vertex 2 of the triangles.
	*\param vec3 Containing the 2D Cartesian coordinates of vertex 3 of the triangles.
	*\param amount1 Barycentric coordinate b2, which expresses the weighting factor towards vertex 2 (specified in vec2).
	*\param amount2 Barycentric coordinate b3, which expresses the weighting factor towards vertex 3 (specified in vec3).
	*\return Vector containing the 2D Cartesian coordinates of the specified point.
	*/
	NEON_STATIC Vector2 barycentric(const Vector2 &vec1, const Vector2 &vec2, const Vector2 &vec3, const nfloat amount1, const nfloat amount2);

	/*!
	Performs a Catmull-Rom interpolation using the specified positions.
	*\param vec1 The first position in the interpolation.
	*\param vec2 The second position in the interpolation.
	*\param vec3 The third position in the interpolation.
	*\param vec4 The fourth position in the interpolation.
	*\param amount Weighting factor.
	*\return Vector that is the result of the Catmull-Rom interpolation.
	*/
	NEON_STATIC Vector2 catmullRom(const Vector2 &vec1, const Vector2 &vec2, const Vector2 &vec3, const Vector2 &vec4, const nfloat amount);

	/*!
	Restricts a value to be within a specified range.
	*\param vec The vector to clamp.
	*\param min The minimum value.
	*\param max The maximum value.
	*\return The clamped value.
	*/
	NEON_STATIC Vector2 clamp(const Vector2 &vec, const Vector2 &min, const Vector2 &max);

	/*!
	Pointer for direct copy.
	*\return Pointer to the first index of the vector.
	*/
	NEON_INLINE const nfloat* data() const { return &X; }

	/*!
	Pointer for direct copy.
	*\return Pointer to the first index of the vector.
	*/
	NEON_INLINE nfloat* data() { return &X; }

	/*!
	Calculates the distance between two vectors.
	*\param vec1 Source vector.
	*\param vec2 Source vector.
	*\return Distance between the two vectors.
	*/
	NEON_STATIC nfloat distance(const Vector2 &vec1, const Vector2 &vec2);

	/*!
	Calculates the distance between two vectors.
	*\param vec Source vector.
	*\return Distance between this vector and the source vector.
	*/
	nfloat distance(const Vector2 &vec) const;

	/*!
	Calculates the distance between two vectors squared.
	*\param vec1 Source vector.
	*\param vec2 Source vector.
	*\return Distance between the source vectors squared.
	*/
	NEON_STATIC nfloat distanceSquared(const Vector2 &vec1, const Vector2 &vec2);

	/*!
	Calculates the distance between two vectors squared.
	*\param vec Source vector.
	*\return Distance between this vector and the source vector squared.
	*/
	nfloat distanceSquared(const Vector2 &vec) const;

	/*!
	Calculates the dot product of two vectors.
	*\param vec1 Source vector.
	*\param vec2 Source vector.
	*\return Dot Product of two vectors.
	*/
	NEON_STATIC nfloat dotProduct(const Vector2 &vec1, const Vector2 &vec2);

	/*!
	Calculates the dot product of two vectors.
	*\param vec Source vector.
	*\return Dot Product of this vector and the source vector.
	*/
	nfloat dotProduct(const Vector2 &vec) const;

	/*!
	Performs a Hermite spline interpolation.
	*\param vec1 Source position vector.
	*\param tang1 Source tangent vector.
	*\param vec2 Source position vector.
	*\param tang2 Source tangent vector.
	*\param amount Weighting factor.
	*\return Result of the Hermit spline interpolation.
	*/
	NEON_STATIC Vector2 hermite(const Vector2 &vec1, const Vector2 &tang1, const Vector2 &vec2, const Vector2 &tang2, const nfloat amount);

	/*!
	Calculates weither two given vectors are perpendicular, which form a 90 degrees angle.
	*\param vec1 Source vector.
	*\param vec2 Source vector.
	*\return true if the dot product of two vectors is 0; false otherwise.
	*/
	NEON_STATIC bool isPerpendicular(const Vector2 &vec1, const Vector2 &vec2);

	/*!
	Calculates weither two given vectors are perpendicular, which form a 90 degrees angle.
	*\param vec Source vector.
	*\return true if the dot product of this and the source vector is 0; false otherwise.
	*/
	bool isPerpendicular(const Vector2 &vec) const;

	/*!
	Calculates the length of the vector.
	*\param vec Source vector.
	*\return Length of the vector.
	*/
	NEON_STATIC nfloat length(const Vector2 &vec);

	/*!
	Calculates the length of the vector.
	*\return Length of the vector.
	*/
	nfloat length() const;

	/*!
	Calculates the length of the vector squared.
	*\return vec Source vector.
	*\return Length of the vector squared.
	*/
	NEON_STATIC nfloat lengthSquared(const Vector2 &vec);

	/*!
	Calculates the length of  the vector squared.
	*\return Length of the vector squared.
	*/
	nfloat lengthSquared() const;

	/*!
	Performs a linear interpolation between two vectors.
	*\param vec1 Source vector.
	*\param vec2 Source vector.
	*\param amount Value between 0 and 1 indicating the weight of vec2.
	*\return Linear interpolation of the two vectors.
	*/
	NEON_STATIC Vector2 lerp(const Vector2 &vec1, const Vector2 &vec2, const nfloat amount);

	/*!
	Returns a vector that contains the highest value from each matching pair of components.
	*\param vec1 Source vector.
	*\param vec2 Source vector.
	*\return The maximum vector.
	*/
	NEON_STATIC Vector2 max(const Vector2 &vec1, const Vector2 &vec2);

	/*!
	Returns a vector that contains the lowest value from each matching pair of components.
	*\param vec1 Source vector.
	*\param vec2 Source vector.
	*\return The minimum vector.
	*/
	NEON_STATIC Vector2 min(const Vector2 &vec1, const Vector2 &vec2);

	/*!
	Creates a unit vector from the specified vector.
	*\param vec Source vector.
	*\return Created unit vector.
	*/
	NEON_STATIC Vector2 normalise(const Vector2 &vec);

	/*!
	Turns the current vector into a unit vector.
	*/
	void normalise();

	/*!
	Returns a Vector2 with all of its components set to Zero.
	*\return A Vector2 with all its components set to Zero.
	*/
	NEON_STATIC Vector2 one;

	/*!
	Determines the reflect vector of the given vector and normal.
	*\param vec Source vector.
	*\param normal Normal of vector.
	*\return Resulting reflect vector.
	*/
	NEON_STATIC Vector2 reflect(const Vector2 &vec, const Vector2 &normal);

	/*!
	Interpolates between two values using a cubic equation.
	*\param vec1 Source vector.
	*\param vec2 Source vector.
	*\param amount Weighting value.
	*\return Interpolated value.
	*/
	NEON_STATIC Vector2 smoothStep(const Vector2 &vec1, const Vector2 &vec2, nfloat amount);

	/*!
	Returns the X unit Vector2 (1, 0).
	*\return The X unit Vector2.
	*/
	NEON_STATIC Vector2 xUnit;

	/*!
	Returns the Y unit Vector2 (0, 1).
	*\return The Y unit Vector2.
	*/
	NEON_STATIC Vector2 yUnit;

	/*!
	Returns a Vector2 with all of its components set to Zero.
	*\return A Vector2 with all its components set to Zero.
	*/
	NEON_STATIC Vector2 zero;

	/*!
	X coordinate.
	*/
	nfloat X;
	/*!
	Y coordinate.
	*/
	nfloat Y;
}; // End of class Vector2.

NEON_INLINE Vector2
operator +(nfloat lhs, const Vector2 &rhs)
{
	return Vector2(lhs + rhs.X, lhs + rhs.Y);
}

NEON_INLINE Vector2
operator -(nfloat lhs, const Vector2 &rhs)
{
	return Vector2(lhs - rhs.X, lhs - rhs.Y);
}

NEON_INLINE Vector2
operator *(nfloat lhs, const Vector2 &rhs)
{
	return Vector2(lhs * rhs.X, lhs * rhs.Y);
}

NEON_INLINE std::ostream&
operator <<(std::ostream &ostr, const Vector2 &vec)
{
	return ostr << std::fixed << std::setprecision(4)
				<< "Vector [ x: " << vec.X << " y: " << vec.Y << " ]" << std::endl;
}

} // End of namespace Neon.

#endif // VECTOR2_HPP
